/*
 * AbstractStringTemplateView.java
 *
 * Copyright 2010 Jack Leow
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package my.edu.clhs.springframework.web.servlet.view.stringtemplate3;

import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import my.edu.clhs.springframework.web.servlet.view.stringtemplate3.support.BindStatusMap;
import my.edu.clhs.springframework.web.servlet.view.stringtemplate3.support.MessagesMap;
import my.edu.clhs.stringtemplate.renderer.CalendarAttributeRenderer;
import my.edu.clhs.stringtemplate.renderer.ClassHierarchyAwareMap;
import my.edu.clhs.stringtemplate.renderer.DateAttributeRenderer;
import my.edu.clhs.stringtemplate.renderer.JodaAbstractInstantAttributeRenderer;
import my.edu.clhs.stringtemplate.renderer.NumberAttributeRenderer;
import my.edu.clhs.stringtemplate.renderer.ServletStringAttributeRenderer;
import my.edu.clhs.stringtemplate.servlet.IncludedResourceContentMap;
import my.edu.clhs.stringtemplate.servlet.ServletContextStringTemplateGroup;
import my.edu.clhs.stringtemplate.servlet.StringTemplateSupport;

import org.antlr.stringtemplate.NoIndentWriter;
import org.antlr.stringtemplate.StringTemplate;
import org.antlr.stringtemplate.StringTemplateGroup;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.base.AbstractInstant;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.support.RequestContext;
import org.springframework.web.servlet.view.AbstractTemplateView;

/**
 * Abstract base {@link View} for all StringTemplate views.
 *
 * TODO revisit design/implementation.
 * TODO make path reference consistent (file path vs StringTemplate style).
 *
 * @author Jack Leow
 * @version 1.0
 * @since 1.0 (Mar 11, 2010, 7:53:55 PM)
 */
public abstract class AbstractStringTemplateView extends AbstractTemplateView {
    private static final String BIND_STATUS_ATTR = "bindStatus";
    private static final String MESSAGE_ATTR = "message";
    private static final String INCLUDED_CONTENT_ATTR = "_servletResources";
    
    private static final Log log =
        LogFactory.getLog(AbstractStringTemplateView.class);
    
    private String encoding;
    
    protected String getEncoding() {
        return encoding;
    }
    
    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }
    
    // TODO consider moving this to a configurer
    private String templateLoaderPath;
    
    protected String getTemplateLoaderPath() {
        return templateLoaderPath;
    }
    
    public void setTemplateLoaderPath(String templateLoaderPath) {
        this.templateLoaderPath = templateLoaderPath;
    }
    
    private static final StringTemplateSupport stringTemplateSupport =
        new StringTemplateSupport();
    
    protected final StringTemplateSupport getStringTemplateSupport() {
        return stringTemplateSupport;
    }
    
    private StringTemplateGroup templateLoader;
    
    protected StringTemplateGroup createTemplateLoader() {
        StringTemplateGroup templateLoader =
            new ServletContextStringTemplateGroup(null, getServletContext());
        String encoding = getEncoding();
        
        if (encoding != null) {
            templateLoader.setFileCharEncoding(encoding);
        }
        
        return templateLoader;
    }
    
    protected StringTemplateGroup getTemplateLoader() {
        if (templateLoader == null) {
            templateLoader = createTemplateLoader();
        }
        
        return templateLoader;
    }
    
    /**
     * Template method that subclasses must implement to load a template for the
     * given name.
     *
     * @param templateName the name of the template to load.
     * @return the resolved template.
     */
    protected abstract StringTemplate loadTemplate(String templateName);
    
    /**
     * Register attribute renderers for the StringTemplate.
     *
     * <p>Subclasses can override this to provide custom attribute renderer
     * registration.
     *
     * TODO consider moving most of this to StringTemplatSupport
     *
     * @param request the current request
     * @param response the current response
     * @param template the template to register the attribute renderers on.
     */
    protected void registerAttributeRenderers(
            HttpServletRequest request, HttpServletResponse response,
            StringTemplate template) {
        log.debug("entering registerAttributeRenderers(" +
            "HttpServletRequest,HttpServletResponse,StringTemplate)");
        // Prepare generic string renderer for all objects
        String mimeType = null;
        try {
            mimeType = response.getContentType();
        } catch (NoSuchMethodError e) {
            // Servlets 2.3 or lower, ignore
        }
        
        String defaultFormatName = null;
        if (mimeType != null) {
            mimeType = mimeType.split(";")[0];
            defaultFormatName =
                "text/html".equals(mimeType) ?
                    ServletStringAttributeRenderer.HTML_ESCAPED_FORMAT :
                mimeType.split("/")[1].indexOf("xml") >= 0 ?
                    ServletStringAttributeRenderer.XML_ESCAPED_FORMAT :
                null;
        }
        
        ServletStringAttributeRenderer stringRenderer;
        if (defaultFormatName != null) {
            stringRenderer = new ServletStringAttributeRenderer(
                request, response, defaultFormatName);
        } else {
            stringRenderer =
                new ServletStringAttributeRenderer(request, response);
        }
        
        // Create the renderer mapping
        Map rendererMapping = new ClassHierarchyAwareMap();
        rendererMapping.put(Object.class, stringRenderer);
        rendererMapping.put(Number.class, new NumberAttributeRenderer());
        rendererMapping.put(Date.class, new DateAttributeRenderer());
        rendererMapping.put(Calendar.class, new CalendarAttributeRenderer());
        // If JodaTime is in the classpath
        try {
            rendererMapping.put(AbstractInstant.class,
                new JodaAbstractInstantAttributeRenderer());
        } catch (NoClassDefFoundError e) {
            // JodaTime is not in the classpath, skip
            log.debug("Joda-Time library unavailable - skipping " +
                "JodaAbstractInstantAttributeRenderer");
        }
        
        template.setAttributeRenderers(rendererMapping);
        log.debug("exiting registerAttributeRenderers(" +
            "HttpServletRequest,HttpServletResponse,StringTemplate)");
    }
    
    protected final void renderMergedTemplateModel(
            Map model,
            HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        StringTemplate template = loadTemplate(getUrl());
        
        if (template != null) {
            Map attributes = new HashMap();
            attributes.putAll(model);
            
            attributes.put(
                INCLUDED_CONTENT_ATTR,
                new IncludedResourceContentMap(request, response));
            
            // Add bindStatus map to attributes if possible
            RequestContext springMacroRequestContext = (RequestContext)
                model.get(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE);
            if (springMacroRequestContext != null) {
                attributes.put(
                    BIND_STATUS_ATTR,
                    new BindStatusMap(springMacroRequestContext));
                attributes.put(
                    MESSAGE_ATTR,
                    new MessagesMap(springMacroRequestContext));
                // TODO backward compatibility, remove ASAP
                attributes.put(
                    "messages",
                    new MessagesMap(springMacroRequestContext));
            }
            
            template.setAttributes(Collections.unmodifiableMap(attributes));
            
            registerAttributeRenderers(request, response, template);
            template.write(new NoIndentWriter(response.getWriter()));
        } else {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
        }
    }
}